﻿namespace LUCAStudios.Silverlight.Controls
{
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Shapes;
    using System.Windows.Media.Imaging;
    using System;

    /// <summary>
    /// Generates Identicon images for use as placeholder when user-defined image is not available.
    /// </summary>
    public class Identicon : Control
    {
        #region fields

        private double _curScale = 1.0;
        private static bool _initialized;
        private static int[] _centerPatchTypes;
        private static byte[] _patchFlags;
        private static Polygon _sLpatch0;
        private static Polygon _sLpatch1;
        private static Polygon _sLpatch10;
        private static Polygon _sLpatch11;
        private static Polygon _sLpatch12;
        private static Polygon _sLpatch13;
        private static Polygon _sLpatch14;
        private static Polygon _sLpatch2;
        private static Polygon _sLpatch3;
        private static Polygon _sLpatch4;
        private static Polygon _sLpatch5;
        private static Polygon _sLpatch6;
        private static Polygon _sLpatch7;
        private static Polygon _sLpatch8;
        private static Polygon _sLpatch9;
        private static Polygon[] _sLpatchTypes;

        #endregion
        // Methods
        
        /// <summary>
        /// Constructor
        /// </summary>
        public Identicon()
        {
            DefaultStyleKey = typeof (Identicon);

            if (_initialized) return;
            _initialized = true;
            Initialize();
        }

        private static void Initialize()
        {
            var polygon = new Polygon();
            var points = new PointCollection
                             {
                                 new Point(0.0, 0.0),
                                 new Point(4.0, 0.0),
                                 new Point(4.0, 4.0),
                                 new Point(0.0, 4.0)
                             };
            polygon.Points = points;
            _sLpatch0 = polygon;
            var polygon2 = new Polygon();
            var points2 = new PointCollection { new Point(0.0, 0.0), new Point(4.0, 0.0), new Point(0.0, 4.0) };
            polygon2.Points = points2;
            _sLpatch1 = polygon2;
            var polygon3 = new Polygon();
            var points3 = new PointCollection { new Point(2.0, 0.0), new Point(4.0, 4.0), new Point(0.0, 4.0) };
            polygon3.Points = points3;
            _sLpatch2 = polygon3;
            var polygon4 = new Polygon();
            var points4 = new PointCollection
                              {
                                  new Point(2.0, 0.0),
                                  new Point(4.0, 2.0),
                                  new Point(2.0, 2.0),
                                  new Point(2.0, 4.0),
                                  new Point(0.0, 2.0),
                                  new Point(2.0, 2.0)
                              };
            polygon4.Points = points4;
            _sLpatch3 = polygon4;
            var polygon5 = new Polygon();
            var points5 = new PointCollection
                              {
                                  new Point(1.0, 0.0),
                                  new Point(3.0, 0.0),
                                  new Point(3.0, 4.0),
                                  new Point(1.0, 4.0)
                              };
            polygon5.Points = points5;
            _sLpatch4 = polygon5;
            var polygon6 = new Polygon();
            var points6 = new PointCollection
                              {
                                  new Point(0.0, 0.0),
                                  new Point(4.0, 2.0),
                                  new Point(4.0, 4.0),
                                  new Point(2.0, 4.0)
                              };
            polygon6.Points = points6;
            _sLpatch5 = polygon6;
            var polygon7 = new Polygon();
            var points7 = new PointCollection
                              {
                                  new Point(2.0, 0.0),
                                  new Point(4.0, 4.0),
                                  new Point(2.0, 4.0),
                                  new Point(3.0, 2.0),
                                  new Point(1.0, 2.0),
                                  new Point(2.0, 4.0),
                                  new Point(0.0, 4.0)
                              };
            polygon7.Points = points7;
            _sLpatch6 = polygon7;
            var polygon8 = new Polygon();
            var points8 = new PointCollection { new Point(0.0, 0.0), new Point(4.0, 2.0), new Point(2.0, 4.0) };
            polygon8.Points = points8;
            _sLpatch7 = polygon8;
            var polygon9 = new Polygon();
            var points9 = new PointCollection
                              {
                                  new Point(1.0, 0.0),
                                  new Point(3.0, 0.0),
                                  new Point(3.0, 2.0),
                                  new Point(1.0, 2.0)
                              };
            polygon9.Points = points9;
            _sLpatch8 = polygon9;
            var polygon10 = new Polygon();
            var points10 = new PointCollection
                               {
                                   new Point(4.0, 0.0),
                                   new Point(0.0, 4.0),
                                   new Point(0.0, 2.0),
                                   new Point(2.0, 2.0),
                                   new Point(2.0, 0.0)
                               };
            polygon10.Points = points10;
            _sLpatch9 = polygon10;
            var polygon11 = new Polygon();
            var points11 = new PointCollection
                               {
                                   new Point(3.0, 0.0),
                                   new Point(4.0, 0.0),
                                   new Point(4.0, 1.0),
                                   new Point(1.0, 4.0),
                                   new Point(0.0, 4.0),
                                   new Point(0.0, 3.0)
                               };
            polygon11.Points = points11;
            _sLpatch10 = polygon11;
            var polygon12 = new Polygon();
            var points12 = new PointCollection
                               {
                                   new Point(0.0, 0.0),
                                   new Point(1.0, 0.0),
                                   new Point(1.0, 2.0),
                                   new Point(2.0, 2.0),
                                   new Point(2.0, 3.0),
                                   new Point(4.0, 3.0),
                                   new Point(4.0, 4.0),
                                   new Point(1.0, 4.0),
                                   new Point(1.0, 3.0),
                                   new Point(0.0, 3.0)
                               };
            polygon12.Points = points12;
            _sLpatch11 = polygon12;
            var polygon13 = new Polygon();
            var points13 = new PointCollection { new Point(2.0, 1.0), new Point(4.0, 3.0), new Point(0.0, 3.0) };
            polygon13.Points = points13;
            _sLpatch12 = polygon13;
            var polygon14 = new Polygon();
            var points14 = new PointCollection { new Point(0.0, 1.0), new Point(2.0, 2.0), new Point(0.0, 3.0) };
            polygon14.Points = points14;
            _sLpatch13 = polygon14;
            var polygon15 = new Polygon();
            var points15 = new PointCollection { new Point(4.0, 1.0), new Point(4.0, 3.0), new Point(2.0, 2.0) };
            polygon15.Points = points15;
            _sLpatch14 = polygon15;
            _sLpatchTypes = new[] { _sLpatch0, _sLpatch1, _sLpatch2, _sLpatch3, _sLpatch4, _sLpatch5, _sLpatch6, _sLpatch7, _sLpatch8, _sLpatch9, _sLpatch10, _sLpatch11, _sLpatch12, _sLpatch13, _sLpatch14, _sLpatch0 };
            _patchFlags = new byte[] { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3 };
            _centerPatchTypes = new[] { 0, 8, 3, 15 };
        }


        /// <summary>
        /// Returns an identicon code derived from the given IP address
        /// </summary>
        /// <param name="ipAddress">The IP address to derive the identicon code from.</param>
        /// <returns></returns>
        public static int ConvertIpToIdenticonCode(string ipAddress)
        {
            return IdenticonUtil.Code(ipAddress);
        }
        /// <summary>
        /// Returns an identicon code derived from the given IP address
        /// </summary>
        /// <param name="ipAddress">The IP address to derive the identicon code from.</param>
        /// <param name="salt">optional salt value to use during code generation.</param>
        /// <returns></returns>
        public static int ConvertIpToIdenticonCode(string ipAddress, string salt)
        {
            IdenticonUtil.Salt = salt;
            var code = IdenticonUtil.Code(ipAddress);
            IdenticonUtil.Salt = default(string);
            return code;
        }

        /// <summary>
        /// Returns an identicon image at the size(width/height in pixels), minimum 12x12, maximum 504x504.  Use multiple of 12 for best results.
        /// </summary>
        /// <param name="code">Identicon code to use a seed for the identicon generator</param>
        /// <param name="size">Size of the image to return (in pixels).  Minimum 12x12, Maximum 504x504.  Use multiple of 12 for best results.</param>
        /// <returns></returns>
        protected WriteableBitmap GenerateIdenticonImage(int code, double size)
        {
            if (size < 12) size = 12;
            if (size > 500) size = 500;
            var factor = Math.Floor(size / 12);
            var cv = GenerateIdenticonCanvas(code, factor);
            return new WriteableBitmap(cv, null);
        }

        private Canvas GenerateIdenticonCanvas(int code, double scale)
        {
            _curScale = scale < 0 ? 1 : scale;
            var g = new Canvas
            {
                Background = new SolidColorBrush(Colors.White),
                Width = 12.0 * _curScale,
                Height = 12.0 * _curScale
            };
            g.Background = new SolidColorBrush(Colors.LightGray);
            var num = 0.0;
            foreach (Polygon t in _sLpatchTypes)
            {
                var element = new Polygon();
                foreach (Point point in t.Points)
                {
                    element.Points.Add(new Point(point.X * _curScale, point.Y * _curScale));
                }
                element.RenderTransformOrigin = new Point(0.0, 0.0);
                element.Fill = new SolidColorBrush(Colors.Blue);
                Canvas.SetTop(element, -5.0 * _curScale);
                Canvas.SetLeft(element, num);
                g.Children.Add(element);
                num += 7 * _curScale;
            }
            int patchNum = _centerPatchTypes[code & 2];
            bool invert = ((code >> 2) & 1) != 0;
            int num4 = (code >> 3) & 15;
            bool flag2 = ((code >> 7) & 1) != 0;
            int turn = (code >> 8) & 3;
            int num6 = (code >> 10) & 15;
            bool flag3 = ((code >> 14) & 1) != 0;
            int num7 = (code >> 15) & 3;
            int num8 = (code >> 0x10) & 0x1f;
            int num9 = (code >> 0x15) & 0x1f;
            int num10 = (code >> 0x1b) & 0x1f;
            Color fore = Color.FromArgb(0xff, (byte)(num10 << 3), (byte)(num9 << 3), (byte)(num8 << 3));
            if (ShowPatch(patchNum, invert))
            {
                SlDrawCenterPatch(g, 6.0 * _curScale, 6.0 * _curScale, patchNum, turn, ref fore);
            }
            if (ShowPatch(num6, flag3))
            {
                SlDrawSidePatch(g, 6.0 * _curScale, 0.0, num6, num7++, ref fore, PatchPosition.Top);
                SlDrawSidePatch(g, (6.0 * _curScale) * 2.0, 6.0 * _curScale, num6, num7++, ref fore, PatchPosition.Right);
                SlDrawSidePatch(g, 6.0 * _curScale, (6.0 * _curScale) * 2.0, num6, num7++, ref fore, PatchPosition.Bottom);
                SlDrawSidePatch(g, 0.0, 6.0 * _curScale, num6, num7, ref fore, PatchPosition.Left);
            }
            if (ShowPatch(num4, flag2))
            {
                SlDrawCornerPatch(g, 0.0, 0.0, num4, turn++, ref fore, PatchPosition.TopLeft);
                SlDrawCornerPatch(g, (6.0 * _curScale) * 2.0, 0.0, num4, turn++, ref fore, PatchPosition.TopRight);
                SlDrawCornerPatch(g, (6.0 * _curScale) * 2.0, (6.0 * _curScale) * 2.0, num4, turn++, ref fore, PatchPosition.BottomRight);
                SlDrawCornerPatch(g, 0.0, (6.0 * _curScale) * 2.0, num4, turn, ref fore, PatchPosition.BottomLeft);
            }
            return g;
        }

        private static bool ShowPatch(int patchNum, bool invert)
        {
            patchNum = patchNum % _sLpatchTypes.Length;
            if ((_patchFlags[patchNum] & 2) != 0)
            {
                invert = !invert;
            }
            return !invert;
        }

        private void SlDrawCenterPatch(Canvas g, double x, double y, int patch, int turn, ref Color fore)
        {
            patch = patch % _sLpatchTypes.Length;
            var element = new Polygon();
            foreach (Point point in _sLpatchTypes[patch].Points)
            {
                element.Points.Add(new Point(point.X * _curScale, point.Y * _curScale));
            }
            element.Fill = new SolidColorBrush(fore);
            element.Stroke = null;
            Point polygonCenter = PolygonCenter;
            Canvas.SetLeft(element, x - polygonCenter.X);
            Canvas.SetTop(element, y - polygonCenter.Y);
            var transform = new RotateTransform
            {
                Angle = turn * 0x2d,
                CenterX = PolygonWidth / 2.0,
                CenterY = PolygonHeight / 2.0
            };
            element.RenderTransform = transform;
            g.Children.Add(element);
        }

        private void SlDrawCornerPatch(Canvas g, double x, double y, int patch, int turn, ref Color fore, PatchPosition pos)
        {
            patch = patch % _sLpatchTypes.Length;
            turn = turn % 4;
            var element = new Polygon();
            foreach (var point in _sLpatchTypes[patch].Points)
            {
                element.Points.Add(new Point(point.X * _curScale, point.Y * _curScale));
            }
            element.Fill = new SolidColorBrush(fore);
            element.Stroke = null;
            element.RenderTransformOrigin = new Point(0.0, 0.0);
            var transform = new RotateTransform
            {
                Angle = turn * 90,
                CenterX = PolygonWidth / 2.0,
                CenterY = PolygonHeight / 2.0
            };
            element.RenderTransform = transform;
            switch (pos)
            {
                case PatchPosition.TopLeft:
                    Canvas.SetLeft(element, x);
                    Canvas.SetTop(element, y);
                    break;

                case PatchPosition.TopRight:
                    Canvas.SetLeft(element, x - (PolygonCenter.X * 2.0));
                    Canvas.SetTop(element, y);
                    break;

                case PatchPosition.BottomRight:
                    Canvas.SetLeft(element, x - (PolygonCenter.X * 2.0));
                    Canvas.SetTop(element, y - (PolygonCenter.Y * 2.0));
                    break;

                case PatchPosition.BottomLeft:
                    Canvas.SetLeft(element, x);
                    Canvas.SetTop(element, y - (PolygonCenter.Y * 2.0));
                    break;
            }
            g.Children.Add(element);
        }

        private void SlDrawSidePatch(Canvas g, double x, double y, int patch, int turn, ref Color fore, PatchPosition pos)
        {
            patch = patch % _sLpatchTypes.Length;
            turn = turn % 4;
            var element = new Polygon();
            foreach (Point point in _sLpatchTypes[patch].Points)
            {
                element.Points.Add(new Point(point.X * _curScale, point.Y * _curScale));
            }
            element.Fill = new SolidColorBrush(fore);
            element.Stroke = null;
            element.RenderTransformOrigin = new Point(0.0, 0.0);
            var transform = new RotateTransform
            {
                Angle = turn * 90,
                CenterX = PolygonWidth / 2.0,
                CenterY = PolygonHeight / 2.0
            };
            element.RenderTransform = transform;
            switch (pos)
            {
                case PatchPosition.Top:
                    Canvas.SetLeft(element, x - PolygonCenter.X);
                    Canvas.SetTop(element, y);
                    break;

                case PatchPosition.Right:
                    Canvas.SetLeft(element, x - (PolygonCenter.X * 2.0));
                    Canvas.SetTop(element, y - PolygonCenter.Y);
                    break;

                case PatchPosition.Bottom:
                    Canvas.SetLeft(element, x - PolygonCenter.X);
                    Canvas.SetTop(element, y - (PolygonCenter.Y * 2.0));
                    break;

                case PatchPosition.Left:
                    Canvas.SetLeft(element, x);
                    Canvas.SetTop(element, y - PolygonCenter.Y);
                    break;
            }
            g.Children.Add(element);
        }

        // Properties
        private Point PolygonCenter
        {
            get
            {
                return new Point((4 * _curScale) / 2, (4 * _curScale) / 2);
            }
        }

        private double PolygonHeight
        {
            get
            {
                return 4 * _curScale;
            }
        }

        private double PolygonWidth
        {
            get
            {
                return 4 * _curScale;
            }
        }

        // Nested Types
        private enum PatchPosition
        {
            Top,
            Right,
            Bottom,
            Left,
            TopLeft,
            TopRight,
            BottomRight,
            BottomLeft
        }
    }
}

